package tests.experiments.moore;


import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.lang.reflect.Field;

import jeconbond.automata.INode;
import jeconbond.automata.IOutcome;
import jeconbond.automata.AutomataUtils;
import jeconbond.experiment.market.resourcerelations.generate.BranchingProdAutomataGenerator;
import jeconbond.experiment.market.resourcerelations.generate.BrosnenEngineInitialiser;
import jeconbond.experiment.market.resourcerelations.generate.GoalGroupDependencyBasedProfitCostGen;
import jeconbond.automata.behaviour.ListPlanWalker;
import jeconbond.economic.EconomicEngine;
import jeconbond.economic.IEconomicEngine;
import jeconbond.economic.agent.*;
import jeconbond.economic.agent.service.IPreActServiceAgent;
import jeconbond.economic.agent.service.IPostActServiceAgent;
import jeconbond.economic.market.agent.*;
import jeconbond.economic.market.agent.service.*;
import jeconbond.economic.market.strategy.IPerformPriceStrategy;
import jeconbond.economic.market.strategy.ProductNecessityMerchant;
import jeconbond.economic.market.strategy.ObserveCostsProfitMerchantDecorator;
import jeconbond.economic.resources.IResourceType;
import jeconbond.economic.resources.ResourceStorrage;
import jeconbond.economic.resources.ResourceType;
import jeconbond.economic.resources.ResourceUtils;
import jeconbond.economic.systems.market.*;
import jeconbond.experiment.JEBExperimentFlow;
import jeconbond.experiment.generate.ProfitableNormFormGenerateDecorator;
import jeconbond.experiment.process.BaseJEBExperimentContext;
import jeconbond.experiment.generate.IEngineInitialiser;
import jeconbond.experiment.market.equilibrium.MooreExperimentSettings;
import jeconbond.experiment.market.equilibrium.generate.MooreAgentsCreator;
import jeconbond.experiment.market.equilibrium.generate.MooreAutomataGenerator;
import jeconbond.experiment.market.equilibrium.generate.MooreEngineInitialiser;
import jeconbond.experiment.market.resourcerelations.BrosnenExperimentSettings;
import jeconbond.experiment.market.equilibrium.process.MooreExperimentContext;
import jeconbond.experiment.market.resourcerelations.process.BrosnenExperimentContext;
import jeconbond.experiment.market.equilibrium.process.IMooreExperimentContext;
import junit.framework.Assert;
import junit.framework.TestCase;
import laboratoryQ.computation.IRandom;
import laboratoryQ.computation.Random;
import laboratoryQ.flatness.Point;

public class TestService extends TestCase {
	public void testSpySellsAgent() {
		IMarketResourceRedestributionSystem mrrs = 
			new DefaultMarketResourceRedestributionSystem();
		int[] stepsCounter_ptr = new int[1];
		IResourceType[] resources = ResourceUtils.generateResources("r", 3);
		SpySellsAgent ssa = new SpySellsAgent(
				mrrs,
				stepsCounter_ptr, 
				resources
		);
		Assert.assertTrue(ssa instanceof IPostActServiceAgent);
		IEconomicAgent ssaASea = ssa;
		ResourceStorrage storr = new ResourceStorrage();
		for (IResourceType rt : resources) {
			storr.storeResource(rt, Double.MAX_VALUE);
		}
		
		IMoneyAccount money = new MoneyAccount();
		IEconomicEngine engine = new EconomicEngine();
		engine.addAgents(ssaASea);
		
		ssaASea.processPreProduceStage();
		mrrs.onSellPerformed(
				new SellProposition(resources[0], 123.0, storr, money),
				1.0,
				null
		);
		ssaASea.processProduceStage();
		ssaASea.processPostProduceStage();
		stepsCounter_ptr[0]++;
		engine.processSteps(1, mrrs);
		Assert.assertEquals(123.0, ssa.getType2priceTrack().get(resources[0]).get(0));
		Assert.assertEquals(1.0, ssa.getType2valueTrack().get(resources[0]).get(0));
		Assert.assertEquals(Double.NaN, ssa.getType2priceTrack().get(resources[1]).get(0));
		
		ssaASea.processPreProduceStage();
		mrrs.onSellPerformed(
				new SellProposition(resources[0], 100.0, storr, money),
				2.0,
				null
		);
		mrrs.onSellPerformed(
				new SellProposition(resources[0], 200.0, storr, money),
				3.0,
				null
		);
		mrrs.onSellPerformed(
				new SellProposition(resources[1], 200.0, storr, money),
				3.123,
				null
		);
		ssaASea.processProduceStage();
		ssaASea.processPostProduceStage();
		stepsCounter_ptr[0]++;
		engine.processSteps(1, mrrs);
		Assert.assertEquals(160.0, ssa.getType2priceTrack().get(resources[0]).get(1));
		Assert.assertEquals(5.0, ssa.getType2valueTrack().get(resources[0]).get(1));
		Assert.assertEquals(200.0, ssa.getType2priceTrack().get(resources[1]).get(1));
		Assert.assertEquals(3.123, ssa.getType2valueTrack().get(resources[1]).get(1));
		
		ssaASea.processPreProduceStage();
		mrrs.onSellPerformed(
				new SellProposition(resources[2], 100.0, storr, money),
				2.0,
				null
		);
		ssaASea.processProduceStage();
		ssaASea.processPostProduceStage();
		ssaASea.processPreProduceStage();
		mrrs.onSellPerformed(
				new SellProposition(resources[2], 200.0, storr, money),
				3.0,
				null
		);
		ssaASea.processProduceStage();
		ssaASea.processPostProduceStage();
		stepsCounter_ptr[0]++;
		engine.processSteps(1, mrrs);
		Assert.assertEquals(160.0, ssa.getType2priceTrack().get(resources[2]).get(2));
		Assert.assertEquals(5.0, ssa.getType2valueTrack().get(resources[2]).get(2));
	}

	public void testSpySellsIgnoring() {
		IMarketResourceRedestributionSystem mrrs =
				new DefaultMarketResourceRedestributionSystem();
		int[] stepsCounter = new int[] { 0 };
		IResourceType[] resources = ResourceUtils.generateResources("r", 9);
		SpySellsAgent ssa = new SpySellsAgent(
				mrrs,
				stepsCounter,
				resources
		);

		ResourceStorrage storr = new ResourceStorrage();
		storr.storeResource(resources[0], 123.0);
		IMoneyAccount money = new MoneyAccount(1e5);

		ssa.processPreProduceStage();

		mrrs.registerProposition(new SellProposition(resources[0], 1.0, storr, money));
		ISellProposition[] props = mrrs.getSellPropositionsList(resources[0]);
		ResourceUtils.processDeal(props[0], 20.0, money, storr, mrrs);

		ssa.processProduceStage();
		ssa.processPostProduceStage();

		stepsCounter[0]++;

		ssa.processPreProduceStage();
		ssa.processProduceStage();
		ssa.processPostProduceStage();

		Assert.assertEquals(20.0, ssa.getType2valueTrack().get(resources[0]).get(0));

		AutomataAgent economicProducer = new AutomataAgent(null);
		economicProducer.setMerchant(
				new ProductNecessityMerchant(
					null,
					money,
					null,
					null,
					null,
					0
				)
		);

		// ### !!!
		ssa.addIgnoreBuyer(economicProducer);

		ssa.processPreProduceStage();

		ResourceUtils.processDeal(props[0], 30.0, money, storr, mrrs);

		ssa.processProduceStage();
		ssa.processPostProduceStage();

		stepsCounter[0]++;

		ssa.processPreProduceStage();
		ssa.processProduceStage();
		ssa.processPostProduceStage();

		Assert.assertEquals(0.0, ssa.getType2valueTrack().get(resources[0]).get(1));
	}

	public void testExceedPurchase() {
		IMarketResourceRedestributionSystem mrrs = 
			new DefaultMarketResourceRedestributionSystem();
		EconomicEngine engine = new EconomicEngine();
		IResourceType[] resources = ResourceUtils.generateResources("r", 5);
		INode rootCust = tests.fakes.GenerateCycleAutomata.generateCustomer(resources, 1, 2, 3, 4, 5);
		INode rootProd = tests.fakes.GenerateCycleAutomata.generateProducer(resources, 1, 2, 3, 4, 5);
		
		List<IOutcome> plan = new ArrayList<IOutcome>();
		AutomataAgent agentCust = root2agent(rootCust, mrrs, plan, false);
		AutomataAgent agentProd = root2agent(rootProd, mrrs, true);
		
		SellInitialResourcesAgent sellInitialResourcesAgent = 
			new SellInitialResourcesAgent(
					mrrs, 
					resources, 
					1, 
					plan.size(), 
					1.1
			);
		tests.fakes.GenerateCycleAutomata.storeInitials(resources, sellInitialResourcesAgent.getInitialStorrage(), 5, 1, 2, 3, 4, 5);
		int steps = plan.size();
		double utiliseExceedPeriod = 10.0;
		double dayBudget = Double.MAX_VALUE;
		int passivePeriod = plan.size() - 1;
		int spyDayCostsTrackLength = 10;
		PurchaseExceedResourcesAgent purchaseExceedResourcesAgent = 
			new PurchaseExceedResourcesAgent(
					mrrs,
					utiliseExceedPeriod,
					dayBudget,
					passivePeriod,
					spyDayCostsTrackLength, 
					resources
					);
		Assert.assertTrue(purchaseExceedResourcesAgent instanceof IPostActServiceAgent);
		engine.addAgents(
				sellInitialResourcesAgent,
				agentCust,
				agentProd,
				purchaseExceedResourcesAgent
		);
		mrrs.addSellsListener(purchaseExceedResourcesAgent);
		engine.processSteps(steps, mrrs);
		ResourceStorrage bufStorr = new ResourceStorrage();
		tests.fakes.GenerateCycleAutomata.storeInitials(resources, bufStorr, 5, 1, 2, 3, 4, 5);
		ResourceUtils.addStorrages(
				bufStorr, 
				tests.fakes.GenerateCycleAutomata.prodPerDayStorr(rootProd)
		);
		ResourceStorrage useDayStorr = tests.fakes.GenerateCycleAutomata.usePerDayStorr(rootCust);
		ResourceUtils.substractStorrages(
				bufStorr,
				useDayStorr,
				false
		);
		ResourceStorrage averStorrage = useDayStorr;
		ResourceUtils.divideStorrage(averStorrage, steps);
		ResourceStorrage exceedStorr = bufStorr;
		exceedStorr.receiveResource(resources[4], 4.0);
		ResourceUtils.substractStorrages(exceedStorr, averStorrage, false);
		ResourceUtils.divideStorrage(exceedStorr, utiliseExceedPeriod);
		ResourceStorrage storr = purchaseExceedResourcesAgent.getPurchasedStorrage();
		for (IResourceType rt : resources) {
			double expectedPurch = exceedStorr.howMuchWasStored(rt);
			double excPurchased = storr.howMuchWasStored(rt);
			assertBound(expectedPurch, excPurchased, 1e-3);
		}
	}

	private void assertBound(double d1, double d2, double EPS) {
		Assert.assertTrue(Math.abs(d1 - d2) < EPS);
	}

	private AutomataAgent root2agent(
			INode root,
			IMarketResourceRedestributionSystem mrrs, 
			boolean constPrice
	) {
		List<IOutcome> plan = new ArrayList<IOutcome>();
		return root2agent(root, mrrs, plan, constPrice);
	}
	
	private AutomataAgent root2agent(
			INode root,
			IMarketResourceRedestributionSystem mrrs, 
			List<IOutcome> plan, 
			final boolean constPrice
	) {
		AutomataAgent agent = new AutomataAgent(root);
		AutomataUtils.easyTracePlan(root, plan);
		ListPlanWalker listPlWl = new ListPlanWalker(
				plan,
				agent.getInputRS(),
				agent.getOutputRS()
		);
		agent.setWalkStrategy(listPlWl);
		IMoneyAccount agentMoney = new MoneyAccount(100.0);
		final ObserveCostsProfitMerchantDecorator observeCPMerch =
			new ObserveCostsProfitMerchantDecorator(
					agent,
					listPlWl,
					mrrs,
					agentMoney,
					1,
					1.0
			);
		((ProductNecessityMerchant)observeCPMerch.getSubmerchant()).setPerformPriceStrategy(
				new IPerformPriceStrategy() {
					@Override
					public double performPrice(IResourceType resourceType) {
						if (constPrice) {
							return 1.0;
						} else {
							return observeCPMerch.performPrice(resourceType);
						}
					}
				}
		);
		agent.setMerchant(observeCPMerch);
		return agent;
	}
	
	public void testSelectPropsStrat() {
		IResourceType wood = new ResourceType("wood");
		ResourceStorrage storrage = new ResourceStorrage();
		storrage.storeResource(wood, 100.0);
		IMoneyAccount money = new MoneyAccount();
		IRandom random = new Random(1234);
		
		IMarketResourceRedestributionSystem mrrs = 
			new DefaultMarketResourceRedestributionSystem(
					new SelectPropsStrat(0.5, random)
			);
		int n = 10;
		for (int i = 0; i < n; i++) {
			ISellProposition sellProposition = new SellProposition(
					wood,
					100.0 + i,
					storrage,
					money
			);
			mrrs.registerProposition(sellProposition);
		}
		ISellProposition[] props = mrrs.getSellPropositionsList(wood);
		Assert.assertEquals(5, props.length);
		ResourceUtils.sortSellPropositions(props);
		Assert.assertTrue(props[0].getPrice() <= props[1].getPrice());
		
		mrrs = 
			new DefaultMarketResourceRedestributionSystem(
					new SelectPropsStrat(-1.0, 2, random)
			);
		for (int i = 0; i < n; i++) {
			ISellProposition sellProposition = new SellProposition(
					wood,
					100.0 + i,
					storrage,
					money
			);
			mrrs.registerProposition(sellProposition);
		}
		props = mrrs.getSellPropositionsList(wood);
		Assert.assertEquals(2, props.length);
		ResourceUtils.sortSellPropositions(props);
		Assert.assertTrue(props[0].getPrice() <= props[1].getPrice());
	}

	public void testAvaluatorOfConcurrentPriceAddedByInitialisers() {
		IEngineInitialiser[] initialisers = new IEngineInitialiser[] {
				new MooreEngineInitialiser(),
				new BrosnenEngineInitialiser(), 
		};
		MooreExperimentSettings msett = new MooreExperimentSettings();
		msett.rrsFactory = new DefaultMarketRRSFactory();
		msett.resources = ResourceUtils.generateResources("r", 1);
		BrosnenExperimentSettings bsett = new BrosnenExperimentSettings();
		bsett.rrsFactory = new DefaultMarketRRSFactory();
		bsett.resources = ResourceUtils.generateResources("r", 1);
		IMooreExperimentContext[] contexts = new IMooreExperimentContext[] {
				new MooreExperimentContext(msett),
				new BrosnenExperimentContext(bsett),
		};

		int n = initialisers.length;
		for (int i = 0; i < n; i++) {
			IEngineInitialiser initialiser = initialisers[i];
			IMooreExperimentContext context = contexts[i];
			EconomicEngine engine = new EconomicEngine();
			initialiser.initialise(engine, new IEconomicProducer[0], context);
			IEconomicAgent avAgent = getAvaluateMarketPriceAgent(context);
			List<IPreActServiceAgent> preActServ = getPreActServ(engine);
			Assert.assertTrue(preActServ.indexOf(avAgent) > -1);
		}
	}

	public void testPurchaseExceedActsInMoore() {
		JEBExperimentFlow experimentFlow = new JEBExperimentFlow();
		MooreExperimentSettings settings = 
			new MooreExperimentSettings();
		settings.initialise();
		
		settings.stepSize = 70;
		settings.stepCount = 10;
		settings.agentsCount = 20;
		settings.chainLength = 5;
		settings.produceProfit = 1.3;
		settings.agentGreedPercent = 2.0;
		settings.initResourceExceedPercent = 0.9;
		settings.initMoney = 50.0;
		settings.observeCostsTraceLength = 1500;
		settings.concurrentPricePercent = 0.5;
		
		settings.purchaseExceedUseTrackLength = 20;
		settings.purchaseExceedPassivePeriod = 0;
		settings.utiliseExceedPeriod = settings.stepSize;
		settings.purchaseExceedDayBudget = 0.0;
		
		settings.rrsFactory = new DefaultMarketRRSFactory();
		settings.resources = ResourceUtils.generateResources("r", 9);
		settings.experiment = experimentFlow;
		settings.randomSeed = 0.0; 
		settings.zeroPoint = new Point(0.0, 0.0);
		settings.selectPropositionsPart = 1.0;
		settings.profitCostManagerFactory = MooreAutomataGenerator.MANAGER_FACTORY_INSTANCE;
		settings.validate();

		ProfitableNormFormGenerateDecorator automataGenerator =
			new ProfitableNormFormGenerateDecorator(new MooreAutomataGenerator());
		MooreAgentsCreator agentCreator = 
			new MooreAgentsCreator();
		MooreEngineInitialiser engineInitialiser = 
			new MooreEngineInitialiser();
		
		BaseJEBExperimentContext experimentContext = (BaseJEBExperimentContext) 
			settings.getExperimentContextFactory().newContext(settings);
		INode[] roots = automataGenerator.generate(experimentContext);
		AutomataAgent[] agents = 
			agentCreator.automata2agents(roots, experimentContext);
		IEconomicEngine engine = new EconomicEngine();
		engineInitialiser.initialise(engine, agents, experimentContext);
		
		engine.processSteps(100, experimentContext.getRRS());
		PurchaseExceedResourcesAgent purchaseExceedAgent = getPurchaseExceedAgent(engine);
		double trackNotesSum = getTrackNotesSum(purchaseExceedAgent);
		Assert.assertTrue(trackNotesSum > 0);
	}

	public void testPurchaseExceedActsInBrosnen() {
		JEBExperimentFlow experimentFlow = new JEBExperimentFlow();
		BrosnenExperimentSettings settings =
			new BrosnenExperimentSettings();
		settings.initialise();

		settings.stepSize = 10;
		settings.stepCount = 10;
		settings.agentsCount = 20;
		settings.initMoney = 50.0;
		settings.initResourceExceedPercent = 1.2;
		settings.antAgentCount = 0;

		settings.chainLength = 5;
		settings.branchesCount = 1;
		settings.postBranchCount = 0;

		settings.agentGreedPercent = 1.2;
		settings.produceProfit = 1.3;
		settings.concurrentPricePercent = 0.3;
		settings.observeCostsTraceLength = 15;

		settings.rrsFactory = new DefaultMarketRRSFactory();
		settings.resources = ResourceUtils.generateResources("r", 9);
		settings.experiment = experimentFlow;
		settings.randomSeed = 0.0;
		settings.zeroPoint = new Point(0.0, 0.0);

		settings.purchaseExceedDayBudget = 0.0;
		settings.purchaseExceedPassivePeriod = settings.stepSize;
		settings.purchaseExceedUseTrackLength = (int) (settings.stepSize * 1.5);
		settings.utiliseExceedPeriod = settings.stepSize;
		settings.selectPropositionsPart = 0.8;
		settings.feramonHistoryLength = 5;
		settings.setCommoditiesDependGroupsPower(10.0);
		settings.setCommoditiesDependGroups(new int[][] {
				new int[] { 0, 1, 2, 3 },
				new int[] { 4, 5, 6, 7, 8 }
		});
		settings.profitCostManagerFactory = GoalGroupDependencyBasedProfitCostGen.FACTORY_INSTANCE;
		settings.goalResourceIndex = 0;
		settings.timeStepX = 30;
		settings.speculantPropCountMarketPart = 0.5;
		settings.speculantGoalValueMarketPart = 0.3;
		settings.validate();

		ProfitableNormFormGenerateDecorator automataGenerator =
				new ProfitableNormFormGenerateDecorator(new BranchingProdAutomataGenerator());
		MooreAgentsCreator agentCreator = new MooreAgentsCreator();
		BrosnenEngineInitialiser engineInitialiser = new BrosnenEngineInitialiser();

		BaseJEBExperimentContext experimentContext = (BaseJEBExperimentContext)
			settings.getExperimentContextFactory().newContext(settings);
		INode[] roots = automataGenerator.generate(experimentContext);
		AutomataAgent[] agents =
				agentCreator.automata2agents(roots, experimentContext);
		IEconomicEngine engine = new EconomicEngine();
		engineInitialiser.initialise(engine, agents, experimentContext);

		engine.processSteps(100, experimentContext.getRRS());
		PurchaseExceedResourcesAgent purchaseExceedAgent = getPurchaseExceedAgent(engine);
		double trackNotesSum = getTrackNotesSum(purchaseExceedAgent);
		Assert.assertTrue(trackNotesSum > 0);
	}

	private double getTrackNotesSum(
			PurchaseExceedResourcesAgent purchaseExceedAgent
	) {
		Map<IResourceType, double[]> resource2useTrack = getResource2useTrack(purchaseExceedAgent);
		double result = 0;
		for (Entry<IResourceType, double[]> entry : resource2useTrack.entrySet()) {
			double[] track = entry.getValue();
			for (double d : track) {
				result += d;
			}
		}
		return result;
	}

	private IEconomicAgent getAvaluateMarketPriceAgent(IMooreExperimentContext context) {
		try {
			Class<?> clazz = context.getClass();
			while (clazz != MooreExperimentContext.class && MooreExperimentContext.class.isAssignableFrom(clazz)) {
				clazz = clazz.getSuperclass();
			}
			Field f = clazz.getDeclaredField("avaluateMarketPriceAgent");
			f.setAccessible(true);
			return (IEconomicAgent) f.get(context);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	private Map<IResourceType, double[]> getResource2useTrack(
			PurchaseExceedResourcesAgent purchaseExceedAgent
	) {
		try {
			Class<? extends PurchaseExceedResourcesAgent> clazz = purchaseExceedAgent.getClass();
			Field f = clazz.getDeclaredField("resource2useTrack");
			f.setAccessible(true);
			return (Map<IResourceType, double[]>) f.get(purchaseExceedAgent);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	private PurchaseExceedResourcesAgent getPurchaseExceedAgent(IEconomicEngine engine) {
		try {
			Class<? extends IEconomicEngine> clazz = engine.getClass();
			Field f = clazz.getDeclaredField("postActServ");
			f.setAccessible(true);
			List<IPostActServiceAgent> pss = (List<IPostActServiceAgent>) f.get(engine);
			for (IPostActServiceAgent ag : pss) {
				if (ag instanceof PurchaseExceedResourcesAgent) {
					return (PurchaseExceedResourcesAgent) ag;
				}
			}
			throw new RuntimeException("Not found");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	private List<IPreActServiceAgent> getPreActServ(EconomicEngine engine) {
		try {
			Class<? extends EconomicEngine> clazz = engine.getClass();
			Field field = clazz.getDeclaredField("preActServ");
			field.setAccessible(true);
			return (List<IPreActServiceAgent>) field.get(engine);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
